package com.g.ocache.impl.store;

import java.util.concurrent.atomic.AtomicBoolean;

import com.g.ocache.Cache;
import com.g.ocache.Store;
import com.g.ocache.StoreEntry;
import com.g.ocache.event.StoreEvent;
import com.g.ocache.event.StoreEventListener;
import com.g.ocache.event.StoreEventPublisher;
import com.g.ocache.event.StoreEventType;
import com.g.ocache.impl.SyncStoreEventPublisher;


/**
 * 
 * @author Jingqi Xu
 */
public abstract class AbstractStore<K, V> implements Store<K, V> {
	//
	private final Cache<K, V> cache;
	private final AtomicBoolean started;
	private final StoreEventPublisher<K, V> publisher;
	
	//
	protected abstract void doStart() throws Exception;
	protected abstract void doStop() throws Exception;
	
	
	/**
	 * 
	 */
	public AbstractStore(Cache<K, V> cache) {
		this(cache, null);
	}
	
	public AbstractStore(Cache<K, V> cache, StoreEventPublisher<K, V> publisher) {
		this.cache = cache;
		this.started = new AtomicBoolean(false);
		this.publisher = (publisher != null ? publisher : new SyncStoreEventPublisher<K, V>());
	}
	
	/**
	 * 
	 */
	public final boolean isRunning() {
		return started.get();
	}
	
	public final void start() throws Exception {
		// Precondition checking
		if(!started.compareAndSet(false, true)) {
			throw new IllegalStateException("the store has already been started");
		}
		
		//
		try {
			doStart();		
		} finally {
			publish(StoreEventType.start);
		}
	}
	
	public final void stop() throws Exception {
		// Precondition checking
		if(!started.compareAndSet(true, false)) {
			return;
		}
		
		//
		try {
			doStop();
		} finally {
			//
			publish(StoreEventType.stop);
			
			//
			if(publisher != null) {
				publisher.stop();
			}
		}
	}
	
	/**
	 * 
	 */
	public final Cache<K, V> getCache() {
		return cache;
	}
	
	public final void publish(StoreEvent<K, V> event) {
		if(publisher == null) {
			return;
		} else {
			event.setStore(this);
			publisher.publish(event);
		}
	}
	
	public final void publish(StoreEventType type) {
		publish(new StoreEvent<K, V>(type));
	}
	
	public final void publish(StoreEventType type, K key, V value) {
		final StoreEvent<K, V> event = new StoreEvent<K, V>(type);
		event.setKey(key);
		event.setValue(value);
		publish(event);
	}
	
	public final void publish(StoreEventType type, StoreEntry<K, V> entry) {
		final StoreEvent<K, V> event = new StoreEvent<K, V>(type);
		event.setKey(entry.getKey());
		event.setValue(entry.getValue());
		publish(event);
	}
	
	public final boolean addStoreEventListener(StoreEventListener<K, V> listener) {
		if(publisher == null) {
			return false;
		} else {
			return publisher.addStoreEventListener(listener);
		}
	}
	
	public final boolean removeStoreEventListener(StoreEventListener<K, V> listener) {
		if(publisher == null) {
			return false;
		} else {
			return publisher.removeStoreEventListener(listener);			
		}
	}
}
